/*-
 * Copyright (c) 2012-2013 Shang-Rong Cai. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $ MTBLAS: exec_pthd.c    Rev. 0.0.13    2013-10-17T16:48+08:00 $
 */

#include <stdlib.h>
#include <pthread.h>

#include <bscerr.h>

#include <exec_isa.h>

#ifdef TRACE_INFO
#include <stdio.h>
#endif

size_t count_distribution_shift(size_t puz, size_t opz, size_t tid)
{
    size_t quo;
    size_t rem;
    size_t psn;  /* Position */

    quo = opz / puz;
    rem = opz % puz;

    if (tid > rem)
    {
        psn = quo * tid + rem;
    }
    else
    {
        psn = (quo + 1) * tid;
    }

    return psn;
}


size_t count_distribution_size(size_t puz, size_t opz, size_t tid)
{
    size_t rem;
    size_t psz;  /* Size of part */

    psz = opz / puz;
    rem = opz % puz;

    if (tid < rem)
    {
        psz += 1;
    }

    return psz;
}


/* Operand Pack Type */
typedef struct
{
    addr_t oc;  /* Pointer to opocde */
    size_t sz;  /* Execution size */

    operand_t *ra;  /* Operand %a */
    operand_t *rb;  /* Operand %b */
    operand_t *rc;  /* Operand %c */
    operand_t *rd;  /* Operand %d */
} operand_pack_t;


void child_execute(void *argx)
{
    operand_pack_t *pk;

    pk = (operand_pack_t*) argx;

    execute(pk->oc, pk->sz, pk->ra, pk->rb, pk->rc, pk->rd);
}


int thread_execute(size_t tz, void *oc, const size_t sz, operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    int ierr = ERRNO_UNDEFINED;
    size_t zi;  /* size_t based loop counter or index */

    pthread_t *thrd;
    operand_pack_t *oppk;  /* Multi-operands */

#ifdef TRACE_INFO
    printf("[F] thread_execute()\n");
#endif

    thrd = (pthread_t*) calloc(tz, sizeof(pthread_t));
    oppk = (operand_pack_t*) calloc(tz, sizeof(operand_pack_t));

    for (zi = 0; zi < tz; zi++)
    {
        oppk[zi].oc = oc;
        oppk[zi].sz = count_distribution_size(tz, sz, zi);
    }

    if (rc == NULL)
    {
        for (zi = 0; zi < tz; zi++)
        {
            oppk[zi].ra = ra + zi;
            oppk[zi].rb = rb + zi;
            oppk[zi].rc = NULL;
            oppk[zi].rd = NULL;
        }
    }
    else if (rd == NULL)
    {
        for (zi = 0; zi < tz; zi++)
        {
            oppk[zi].ra = ra + zi;
            oppk[zi].rb = rb + zi;
            oppk[zi].rc = rc + zi;
            oppk[zi].rd = NULL;
        }
    }
    else
    {
        for (zi = 0; zi < tz; zi++)
        {
            oppk[zi].ra = ra + zi;
            oppk[zi].rb = rb + zi;
            oppk[zi].rc = rc + zi;
            oppk[zi].rd = rd + zi;
        }
    }

    for (zi = 0; zi < tz; zi++)
    {
        ierr = pthread_create(thrd + zi, NULL, (void*) child_execute, (void*) &oppk[zi]);
    }

    for (zi = 0; zi < tz; zi++)
    {
        ierr = pthread_join(thrd[zi], NULL);
    }

    free(thrd);
    free(oppk);

#ifdef TRACE_INFO
    printf("    thread_execute() Passed!\n");
#endif

    return ierr;
}


/* Check and correct the number of threads */
size_t proper_thrz(size_t thrz)
{
#ifdef TRACE_INFO
    printf("[F] proper_thrz()\n");
#endif

    if (thrz < THREADS_MIN)
    {
        thrz = THREADS_MIN;
    }

    if (thrz > THREADS_MAX)
    {
        thrz = THREADS_MAX;
    }

#ifdef TRACE_INFO
    printf("    proper_thrz() Passed!\n");
#endif

    return thrz;
}


/* Automatically Distributing Operands Target Address */
int operands_distributor(size_t thrz, size_t sz, void *addr, step_t step, operand_t *ra)
{
    int ierr = ERRNO_UNDEFINED;
    size_t zi;  /* size_t based loop counter or index */

#ifdef TRACE_INFO
    printf("[F] operands_distributor()\n");
#endif

    for (zi = 0; zi < thrz; zi++)
    {
        ra[zi].addr = addr + count_distribution_shift(thrz, sz, zi) * step;
        ra[zi].step = step;
    }

#ifdef TRACE_INFO
    printf("    operands_distributor() Passed!\n");
#endif

    return ierr;
}


/* Automatically Set Collecting Operands Target Address */
int operands_collector(size_t thrz, size_t typz, void *addr, operand_t *ra)
{
    int ierr = ERRNO_UNDEFINED;
    size_t zi;  /* size_t based loop counter or index */

#ifdef TRACE_INFO
    printf("[F] operands_collector()\n");
#endif

#ifndef NOERR_CHKS
    if (thrz == 0)
    {
        ierr = ERRNO_OUT_OF_RANGE_PUZ;
        goto EXIT_OPERANDS_COLLECTOR;
    }

    if (typz == 0)
    {
        ierr = ERRNO_OUT_OF_RANGE_SIZE;
        goto EXIT_OPERANDS_COLLECTOR;
    }
#endif

    for (zi = 0; zi < thrz; zi++)
    {
        ra[zi].addr = addr + zi * typz;
        ra[zi].step = 0;
    }

    EXIT_OPERANDS_COLLECTOR:
#ifdef TRACE_INFO
    printf("    operands_collector() Passed!\n");
#endif

    return ierr;
}


/* A Betch Set of Operands Data for Address and Step */
int operands_batch_set(size_t thrz, void *addr, step_t step, operand_t *ra)
{
    int ierr = ERRNO_UNDEFINED;
    size_t zi;  /* size_t based loop counter or index */

#ifdef TRACE_INFO
    printf("[F] operands_batch_set()\n");
#endif

#ifndef NOERR_CHKS
    if (thrz == 0)
    {
        ierr = ERRNO_OUT_OF_RANGE_PUZ;
        goto EXIT_operands_batch_set;
    }
#endif

    for (zi = 0; zi < thrz; zi++)
    {
        ra[zi].addr = addr;
        ra[zi].step = step;
    }

    EXIT_operands_batch_set:
#ifdef TRACE_INFO
    printf("    operands_batch_set() Passed!\n");
#endif

    return ierr;
}


int operands_create_2(size_t thrz, operand_t **ra, operand_t **rb)
{
    int ierr = ERRNO_UNDEFINED;

#ifdef TRACE_INFO
    printf("[F] operands_create_2()\n");
#endif

    *ra = (operand_t*) calloc(thrz, sizeof(operand_t));
    *rb = (operand_t*) calloc(thrz, sizeof(operand_t));

#ifdef TRACE_INFO
    printf("    operands_create_2() Passed!\n");
#endif

    return ierr;
}


int operands_create_3(size_t thrz, operand_t **ra, operand_t **rb, operand_t **rc)
{
    int ierr = ERRNO_UNDEFINED;

#ifdef TRACE_INFO
    printf("[F] operands_create_3()\n");
#endif

    *ra = (operand_t*) calloc(thrz, sizeof(operand_t));
    *rb = (operand_t*) calloc(thrz, sizeof(operand_t));
    *rc = (operand_t*) calloc(thrz, sizeof(operand_t));

#ifdef TRACE_INFO
    printf("    operands_create_3() Passed!\n");
#endif

    return ierr;
}


int operands_create_4(size_t thrz, operand_t **ra, operand_t **rb, operand_t **rc, operand_t **rd)
{
    int ierr = ERRNO_UNDEFINED;

#ifdef TRACE_INFO
    printf("[F] operands_create_4()\n");
#endif

    *ra = (operand_t*) calloc(thrz, sizeof(operand_t));
    *rb = (operand_t*) calloc(thrz, sizeof(operand_t));
    *rc = (operand_t*) calloc(thrz, sizeof(operand_t));
    *rd = (operand_t*) calloc(thrz, sizeof(operand_t));

#ifdef TRACE_INFO
    printf("    operands_create_4() Passed!\n");
#endif

    return ierr;
}


int operands_delete_2(operand_t *ra, operand_t *rb)
{
    int ierr = ERRNO_UNDEFINED;

#ifdef TRACE_INFO
    printf("[F] operands_delete_2()\n");
#endif

    free(ra);
    free(rb);

#ifdef TRACE_INFO
    printf("    operands_delete_2() Passed!\n");
#endif

    return ierr;
}


int operands_delete_3(operand_t *ra, operand_t *rb, operand_t *rc)
{
    int ierr = ERRNO_UNDEFINED;

#ifdef TRACE_INFO
    printf("[F] operands_delete_3()\n");
#endif

    free(ra);
    free(rb);
    free(rc);

#ifdef TRACE_INFO
    printf("    operands_delete_3() Passed!\n");
#endif

    return ierr;
}


int operands_delete_4(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    int ierr = ERRNO_UNDEFINED;

#ifdef TRACE_INFO
    printf("[F] operands_delete_4()\n");
#endif

    free(ra);
    free(rb);
    free(rc);
    free(rd);

#ifdef TRACE_INFO
    printf("    operands_delete_4() Passed!\n");
#endif

    return ierr;
}


int operands_assign_single_opaddr(size_t thrz, operand_t *op, void *addr)
{
    int ierr = ERRNO_UNDEFINED;
    size_t zi;

#ifdef TRACE_INFO
    printf("[F] operands_assign_single_opaddr()\n");
#endif

    for (zi = 0; zi < thrz; zi++)
    {
        op[zi].addr = (addr_t) addr;
        op[zi].step = 0;
    }

#ifdef TRACE_INFO
    printf("    operands_assign_single_opaddr() Passed!\n");
#endif

    return ierr;
}


int operands_assign_opchunks(size_t thrz, operand_t *op, opchunk_t *chunk)
{
    int ierr = ERRNO_UNDEFINED;
    size_t zi;

#ifdef TRACE_INFO
    printf("[F] operands_assign_opchunks()\n");
#endif

    for (zi = 0; zi < thrz; zi++)
    {
        op[zi].addr = (addr_t) &chunk[zi];
        op[zi].step = 0;
    }

#ifdef TRACE_INFO
    printf("    operands_assign_opchunks() Passed!\n");
#endif

    return ierr;
}


int opchunks_create_3(size_t thrz, opchunk_t **pk_a, opchunk_t **pk_c, opchunk_t **pk_d)
{
    int ierr = ERRNO_UNDEFINED;

#ifdef TRACE_INFO
    printf("[F] opchunks_create_3()\n");
#endif

    *pk_a = (opchunk_t*) calloc(thrz, sizeof(opchunk_t));
    *pk_c = (opchunk_t*) calloc(thrz, sizeof(opchunk_t));
    *pk_d = (opchunk_t*) calloc(thrz, sizeof(opchunk_t));

#ifdef TRACE_INFO
    printf("    opchunks_create_3() Passed!\n");
#endif

    return ierr;
}


int opchunks_delete_3(opchunk_t *pk_a, opchunk_t *pk_c, opchunk_t *pk_d)
{
    int ierr = ERRNO_UNDEFINED;

#ifdef TRACE_INFO
    printf("[F] opchunks_delete_3()\n");
#endif

    free(pk_a);
    free(pk_c);
    free(pk_d);

#ifdef TRACE_INFO
    printf("    opchunks_delete_3() Passed!\n");
#endif

    return ierr;
}


int opchunks_set_opcode(size_t thrz, opchunk_t *chunk, addr_t oc)
{
    int ierr = ERRNO_UNDEFINED;
    size_t zi;  /* size_t based loop counter or index */

#ifdef TRACE_INFO
    printf("[F] opchunks_set_opcode()\n");
#endif

    for (zi = 0; zi < thrz; zi++)
    {
        chunk[zi].oc = oc;
    }

#ifdef TRACE_INFO
    printf("    opchunks_set_opcode() Passed!\n");
#endif

    return ierr;
}


int opchunks_set_opsize(size_t thrz, opchunk_t *chunk, size_t sz)
{
    int ierr = ERRNO_UNDEFINED;
    size_t zi;  /* size_t based loop counter or index */

#ifdef TRACE_INFO
    printf("[F] opchunks_set_opsize()\n");
#endif

    for (zi = 0; zi < thrz; zi++)
    {
        chunk[zi].sz = sz;
    }

#ifdef TRACE_INFO
    printf("    opchunks_set_opsize() Passed!\n");
#endif

    return ierr;
}


int opchunks_set_ra_addr_mono(size_t thrz, opchunk_t *chunk, addr_t addr)
{
    int ierr = ERRNO_UNDEFINED;
    size_t zi;  /* size_t based loop counter or index */

#ifdef TRACE_INFO
    printf("[F] opchunks_set_ra_addr_mono()\n");
#endif

    for (zi = 0; zi < thrz; zi++)
    {
        chunk[zi].ra.addr = (addr_t) addr;
    }

#ifdef TRACE_INFO
    printf("    opchunks_set_ra_addr_mono() Passed!\n");
#endif

    return ierr;
}


int opchunks_set_ra_addr_dist(size_t thrz, opchunk_t *chunk, size_t sz, addr_t addr, step_t stride)
{
    int ierr = ERRNO_UNDEFINED;
    size_t zi;  /* size_t based loop counter or index */

#ifdef TRACE_INFO
    printf("[F] opchunks_set_ra_addr_dist()\n");
#endif

    for (zi = 0; zi < thrz; zi++)
    {
        chunk[zi].ra.addr = addr + count_distribution_shift(thrz, sz, zi) * stride;
    }

#ifdef TRACE_INFO
    printf("    opchunks_set_ra_addr_dist() Passed!\n");
#endif

    return ierr;
}


int opchunks_set_ra_step_mono(size_t thrz, opchunk_t *chunk, step_t step)
{
    int ierr = ERRNO_UNDEFINED;
    size_t zi;  /* size_t based loop counter or index */

#ifdef TRACE_INFO
    printf("[F] opchunks_set_ra_step_mono()\n");
#endif

    for (zi = 0; zi < thrz; zi++)
    {
        chunk[zi].ra.step = step;
    }

#ifdef TRACE_INFO
    printf("    opchunks_set_ra_step_mono() Passed!\n");
#endif

    return ierr;
}


int opchunks_set_rb_addr_mono(size_t thrz, opchunk_t *chunk, addr_t addr)
{
    int ierr = ERRNO_UNDEFINED;
    size_t zi;  /* size_t based loop counter or index */

#ifdef TRACE_INFO
    printf("[F] opchunks_set_rb_addr_mono()\n");
#endif

    for (zi = 0; zi < thrz; zi++)
    {
        chunk[zi].rb.addr = (addr_t) addr;
    }

#ifdef TRACE_INFO
    printf("    opchunks_set_rb_addr_mono() Passed!\n");
#endif

    return ierr;
}


int opchunks_set_rb_addr_dist(size_t thrz, opchunk_t *chunk, size_t sz, addr_t addr, step_t stride)
{
    int ierr = ERRNO_UNDEFINED;
    size_t zi;  /* size_t based loop counter or index */

#ifdef TRACE_INFO
    printf("[F] opchunks_set_rb_addr_dist()\n");
#endif

    for (zi = 0; zi < thrz; zi++)
    {
        chunk[zi].rb.addr = addr + count_distribution_shift(thrz, sz, zi) * stride;
    }

#ifdef TRACE_INFO
    printf("    opchunks_set_rb_addr_dist() Passed!\n");
#endif

    return ierr;
}


int opchunks_set_rb_step_mono(size_t thrz, opchunk_t *chunk, step_t step)
{
    int ierr = ERRNO_UNDEFINED;
    size_t zi;  /* size_t based loop counter or index */

#ifdef TRACE_INFO
    printf("[F] opchunks_set_rb_step_mono()\n");
#endif

    for (zi = 0; zi < thrz; zi++)
    {
        chunk[zi].rb.step = step;
    }

#ifdef TRACE_INFO
    printf("    opchunks_set_rb_step_mono() Passed!\n");
#endif

    return ierr;
}


int opchunks_set_rc_addr_mono(size_t thrz, opchunk_t *chunk, addr_t addr)
{
    int ierr = ERRNO_UNDEFINED;
    size_t zi;  /* size_t based loop counter or index */

#ifdef TRACE_INFO
    printf("[F] opchunks_set_rc_addr_mono()\n");
#endif

    for (zi = 0; zi < thrz; zi++)
    {
        chunk[zi].rc.addr = (addr_t) addr;
    }

#ifdef TRACE_INFO
    printf("    opchunks_set_rc_addr_mono() Passed!\n");
#endif

    return ierr;
}


int opchunks_set_rc_addr_dist(size_t thrz, opchunk_t *chunk, size_t sz, addr_t addr, step_t stride)
{
    int ierr = ERRNO_UNDEFINED;
    size_t zi;  /* size_t based loop counter or index */

#ifdef TRACE_INFO
    printf("[F] opchunks_set_rc_addr_dist()\n");
#endif

    for (zi = 0; zi < thrz; zi++)
    {
        chunk[zi].rc.addr = addr + count_distribution_shift(thrz, sz, zi) * stride;
    }

#ifdef TRACE_INFO
    printf("    opchunks_set_rc_addr_dist() Passed!\n");
#endif

    return ierr;
}


int opchunks_set_rc_step_mono(size_t thrz, opchunk_t *chunk, step_t step)
{
    int ierr = ERRNO_UNDEFINED;
    size_t zi;  /* size_t based loop counter or index */

#ifdef TRACE_INFO
    printf("[F] opchunks_set_rc_step_mono()\n");
#endif

    for (zi = 0; zi < thrz; zi++)
    {
        chunk[zi].rc.step = step;
    }

#ifdef TRACE_INFO
    printf("    opchunks_set_rc_step_mono() Passed!\n");
#endif

    return ierr;
}


int opchunks_set_rd_addr_mono(size_t thrz, opchunk_t *chunk, addr_t addr)
{
    int ierr = ERRNO_UNDEFINED;
    size_t zi;  /* size_t based loop counter or index */

#ifdef TRACE_INFO
    printf("[F] opchunks_set_rd_addr_mono()\n");
#endif

    for (zi = 0; zi < thrz; zi++)
    {
        chunk[zi].rd.addr = (addr_t) addr;
    }

#ifdef TRACE_INFO
    printf("    opchunks_set_rd_addr_mono() Passed!\n");
#endif

    return ierr;
}


int opchunks_set_rd_addr_dist(size_t thrz, opchunk_t *chunk, size_t sz, addr_t addr, step_t stride)
{
    int ierr = ERRNO_UNDEFINED;
    size_t zi;  /* size_t based loop counter or index */

#ifdef TRACE_INFO
    printf("[F] opchunks_set_rd_addr_dist()\n");
#endif

    for (zi = 0; zi < thrz; zi++)
    {
        chunk[zi].rd.addr = addr + count_distribution_shift(thrz, sz, zi) * stride;
    }

#ifdef TRACE_INFO
    printf("    opchunks_set_rd_addr_dist() Passed!\n");
#endif

    return ierr;
}


int opchunks_set_rd_step_mono(size_t thrz, opchunk_t *chunk, step_t step)
{
    int ierr = ERRNO_UNDEFINED;
    size_t zi;  /* size_t based loop counter or index */

#ifdef TRACE_INFO
    printf("[F] opchunks_set_rd_step_mono()\n");
#endif

    for (zi = 0; zi < thrz; zi++)
    {
        chunk[zi].rd.step = step;
    }

#ifdef TRACE_INFO
    printf("    opchunks_set_rd_step_mono() Passed!\n");
#endif

    return ierr;
}
